using System;
using System.Collections.Generic;
using System.Linq;
using PropertyExpression.Common;

namespace PropertyExpression.ReactiveRelation
{
    public static partial class Value
    {
        private static IPropertyValue<TSubProperty> CreatePropertyValue<TEntity, TSubProperty>(
            this IPropertyValue<TEntity> propertyValue,
            IPropertyExpressionProxy<TEntity, TSubProperty> propertyExpressionProxy)
        {
            return new SubPropertyValue<TEntity, TSubProperty>(propertyValue, propertyExpressionProxy);
        }

        private class SubPropertyValue<TEntity, TSubProperty> : IPropertyValue<TSubProperty>
        {
            private readonly IPropertyValue<TEntity> propertyValue;
            private readonly IPropertyExpressionProxy<TEntity, TSubProperty> propertyExpressionProxy;
            private readonly ReverseEntitiesProvider<TEntity, TSubProperty> reverseEntitiesProvider;

            public SubPropertyValue(IPropertyValue<TEntity> propertyValue,
                IPropertyExpressionProxy<TEntity, TSubProperty> propertyExpressionProxy)
            {
                this.propertyValue = propertyValue;
                this.propertyExpressionProxy = propertyExpressionProxy;
                reverseEntitiesProvider = new ReverseEntitiesProvider<TEntity, TSubProperty>(
                    () => this.propertyExpressionProxy,
                    (reverseEntity, previousValueProcessor) =>
                    this.propertyValue.GetReverseEntities(reverseEntity, previousValueProcessor)
                    );
            }

            public IEnumerable<object> GetReverseEntities(object sender, 
                IPreviousValueProcessor previousValueProcessor)
            {
                return reverseEntitiesProvider.GetReverseEntities(sender, previousValueProcessor);
            }

            public IEnumerable<object> GetReverseEntitiesForCollection(object sender, 
                IPreviousValueProcessor previousValueProcessor)
            {
                return reverseEntitiesProvider.GetReverseEntitiesForCollection(sender, previousValueProcessor);
            }

            public IOptionalValue<TResult> ProcessOneToManyCollectionNewItem<TResult>(
                Func<object, TResult> addEntityFunc,
                Func<object, TResult> removeEntityFunc,
                Func<TResult> notExistFunc,
                IPropertySettor propertySettor,
                IOneToManyCollectionNewItemProcessor oneToManyCollectionNewItemProcessor)
            {
                return GetEntity(propertySettor, oneToManyCollectionNewItemProcessor).ProcessValue(
                    entity => propertyExpressionProxy.ProcessOneToManyCollectionNewItem(
                        () => entity,
                        oneToManyCollectionNewItemProcessor,
                        addEntityFunc,
                        removeEntityFunc,
                        notExistFunc)
                    );
            }

            public IEnumerable<ICell> GetReverseCells()
            {
                return Concat(() => new ReverseCell(this));
            }

            public IEnumerable<ICell> GetCells()
            {
                return Concat(() => new Cell(this));
            }

            public IOptionalValue<TSubProperty> GetValue(IPropertySettor propertySettor, 
                IOneToManyCollectionNewItemProcessor oneToManyCollectionNewItemProcessor)
            {
                return GetEntity(propertySettor, oneToManyCollectionNewItemProcessor).ProcessValue(
                    entity => ReferenceEquals(entity, null)
                                  ? default(TSubProperty)
                                  : new ValueInfo(propertySettor, this, entity).GetValue()
                    );
            }

            public IPropertyValue<TProperty> GetPropertyValue<TProperty>(
                IPropertyExpressionProxy<TSubProperty, TProperty> subPropertyExpressionProxy)
            {
                return this.CreatePropertyValue(subPropertyExpressionProxy);
            }

            private IEnumerable<ICell> Concat(Func<ICell> cellFunc)
            {
                return propertyValue.GetCells().Concat(new[] { cellFunc() });
            }

            private IOptionalValue<TEntity> GetEntity(IPropertySettor propertySettor,
                IOneToManyCollectionNewItemProcessor oneToManyCollectionNewItemProcessor)
            {
                return propertyValue.GetValue(propertySettor, oneToManyCollectionNewItemProcessor);
            }

            private class Cell : ICell
            {
                private readonly SubPropertyValue<TEntity, TSubProperty> parent;

                public Cell(SubPropertyValue<TEntity, TSubProperty> parent)
                {
                    this.parent = parent;
                }

                public string PropertyName
                {
                    get
                    {
                        return parent.propertyExpressionProxy.GetPropertyName();
                    }
                }

                public string EntityFullName
                {
                    get
                    {
                        return typeof(TEntity).FullName;
                    }
                }

                public IEnumerable<object> GetReverseEntities(object sender, 
                    IPreviousValueProcessor previousValueProcessor)
                {
                    return parent.propertyValue.GetReverseEntities(sender, previousValueProcessor);
                }
            }

            private class ReverseCell : ICell
            {
                private readonly SubPropertyValue<TEntity, TSubProperty> parent;
                private readonly PropertyNameAndEntityFullNameProvider provider;

                public ReverseCell(SubPropertyValue<TEntity, TSubProperty> parent)
                {
                    this.parent = parent;
                    provider = new PropertyNameAndEntityFullNameProvider(parent.propertyExpressionProxy);
                }

                private class PropertyNameAndEntityFullNameProvider
                {
                    private readonly string propertyName;
                    private readonly string entityFullName;

                    public PropertyNameAndEntityFullNameProvider(
                        IPropertyExpressionProxy<TEntity, TSubProperty> propertyExpressionProxy)
                    {
                        propertyName = propertyExpressionProxy.GetReversePropertyInfoName();
                        entityFullName = propertyExpressionProxy.GetReturnValueTypeGenericArgumentFullName();
                    }

                    public string PropertyName
                    {
                        get { return propertyName; }
                    }

                    public string EntityFullName
                    {
                        get { return entityFullName; }
                    }
                }

                public string PropertyName
                {
                    get { return provider.PropertyName; }
                }

                public string EntityFullName
                {
                    get { return provider.EntityFullName; }
                }

                public IEnumerable<object> GetReverseEntities(object sender,
                    IPreviousValueProcessor previousValueProcessor)
                {
                    return parent.GetReverseEntitiesForCollection(sender, previousValueProcessor);
                }
            }

            private class ValueInfo : IValueInfo<TSubProperty, TEntity>
            {
                private readonly IPropertySettor propertySettor;
                private readonly SubPropertyValue<TEntity, TSubProperty> parent;
                private readonly TEntity entity;

                public ValueInfo(
                    IPropertySettor propertySettor,
                    SubPropertyValue<TEntity, TSubProperty> parent,
                    TEntity entity)
                {
                    this.propertySettor = propertySettor;
                    this.parent = parent;
                    this.entity = entity;
                }

                public IPropertySettor PropertySettor
                {
                    get { return propertySettor; }
                }

                public IEntityPropertyProxy EntityPropertyProxy
                {
                    get { return new PropertyProxy(parent, entity); }
                }

                public IPropertyExpressionProxy<TEntity, TSubProperty> PropertyExpressionProxy
                {
                    get { return parent.propertyExpressionProxy; }
                }

                public TEntity Entity
                {
                    get { return entity; }
                }
            }

            private class PropertyProxy : IEntityPropertyProxy
            {
                private readonly SubPropertyValue<TEntity, TSubProperty> parent;
                private readonly TEntity entity;

                public PropertyProxy(SubPropertyValue<TEntity, TSubProperty> parent, TEntity entity)
                {
                    this.parent = parent;
                    this.entity = entity;
                }

                public object Entity
                {
                    get { return entity; }
                }

                public string PropertyName
                {
                    get { return parent.propertyExpressionProxy.GetPropertyName(); }
                }
            }
        }
    }
}
